Tutorial 6: ViewSets & Routers

REST 框架包含一个 ViewSets 的抽象, 它可以让开发者将精力集中在构建API的状态和交互上, 同时帮助开发者, 基于共同约定, 自动处理 URL 构建.

ViewSet 类几乎和 View 类一样, 除了它提供的 read 或者 update 操作, 而不是像 getput 一样的方法.

一个 ViewSet 类在它被实例化成一个视图集合的最后时刻, 通过一个处理复杂 URL 配置的 Router 类绑定, 且只绑定一个方法集合.

使用 ViewSets 重构

首先使用单个 UserViewSet 视图重构 UserListUserDetail 视图.

文件 snippets/views.py

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    This viewset automatically provides `list` and `detail` actions.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

这里我们使用 ReadOnlyModelViewSet 类自动提供默认的 'read-only' 操作. 我们需要像使用常规视图一样, 设置 querysetserializer_class 属性, 但是我们不再需要为两个分开的类提供相同的信息.

接下来替换 SnippetList, SnippetDetail and SnippetHighlight 视图类.

from rest_framework.decorators import detail_route
from rest_framework.response import Response

class SnippetViewSet(viewsets.ModelViewSet):
    """
    This viewset automatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.

    Additionally we also provide an extra `highlight` action.
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

这一次我们使用了 ModelViewSet 类获得默认的完整的读写操作.

注意, 我们同时使用了 @detail_route 装饰器, 用于创建一个自定义动作, 即 highlight. 这个装饰器可以用于添加任何不适合 create/update/delete 方式的自定义端点.

使用 @detail_route 装饰器自定义的动作默认会响应 GET 请求. 如果我们需要动作响应 POST 请求, 我们可以使用 methods 参数.

自定义动作的默认 URLs 取决于它们的名字. 如果你想改变url构建方法, 你可以在使用装饰器的时候传入 url_path 关键字参数.

明确绑定 ViewSets 到 URLs

处理方法, 只会按照我们的 URL 配置对相应方法进行绑定. 我们为我们的 ViewSets 显示地创建一个视图集合, 来看看发生了什么.

snippets/urls.py 文件, 我们绑定我们的 ViewSet 类到一组具体的视图.

from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

注意我们如何从每个 ViewSet 类, 通过绑定http方法到响应的动作来创建多个视图.

现在, 我们将我们的资源绑定到了具体的视图, 我们可以像往常一样将我们的视图注册到url配置中

urlpatterns = format_suffix_patterns([
    url(r'^$', api_root),
    url(r'^snippets/$', snippet_list, name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
    url(r'^users/$', user_list, name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])

使用 Routers

因为我们使用 ViewSet 代替 View, 实际上我们不需要自己设计 URL 配置. 我们可以通过 Router 类, 将资源(resources), 视图(views), urls 自动联系起来. 我们只需要使用一个路由注册合适的视图集合.

重写 snippets/urls.py

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from snippets import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router.
urlpatterns = [
    url(r'^', include(router.urls))
]

使用 router 注册的视图集合提供一个 urlpattern. 包括两个参数 - 视图的URl前缀和视图集合本身.

我们使用的默认 DefaultRouter 类也会自动为我们创建 API 根视图. 现在我们可以从 views 模块中删除 api_root 方法

权衡使用 views 和 viewsets

viewsets 是一个非常有用的抽象. 它可以确保 URL 原型和你的 API 保持一致, 最大限度的减少代码量, 允许你将精力放在 API 的交互和表示上, 而不是放在编写 URL conf 上.

这并不意味在所有地方都要使用 viewsets. 在使用基于类的视图和基于函数的视图时, 需要进行权衡. 使用 viewsets 没有单独构建 views 明确.

在教程第7部分, 我们将介绍, 如何添加一个 APP schema, 并使用客户端库或命令行工具与我们的 API 进行交互.